package business

import (
	"crypto/sha256"
	"encoding/json"
	"fcs-api/src/app/model"
	"fcs-api/src/client/kubernetes"
	"fmt"
	kongConfigurationAPIV1 "github.com/kong/kubernetes-ingress-controller/pkg/apis/configuration/v1"
	"io"
	corev1 "k8s.io/api/core/v1"
	networkingv1 "k8s.io/api/networking/v1"
	apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
	"k8s.io/apimachinery/pkg/api/errors"
	extentionsv1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/util/intstr"
	"strings"
)

// 创建并获取账号对应的唯一 Ingress default-<accountId>，并创建路由规则
func CreateHttpTriggerIngress(fcClient *kubernetes.FcClient, triggerInfo *model.Trigger) error {
	namespace := model.Base.GatewayNamespace
	ingressName := getHttpTriggerIngressName(triggerInfo)
	ingressPath := getHttpTriggerIngressPath(triggerInfo)
	serviceName := getHttpTriggerServiceName(triggerInfo)
	pathTypePrefix := networkingv1.PathTypePrefix
	ingressRoute := networkingv1.HTTPIngressPath{
		Path:     ingressPath,
		PathType: &pathTypePrefix,
		Backend: networkingv1.IngressBackend{
			Service: &networkingv1.IngressServiceBackend{
				Name: serviceName,
				Port: networkingv1.ServiceBackendPort{
					Number: 80,
				},
			},
		},
	}
	// 先查询 Ingress 是否已经创建
	ingress, err := fcClient.GetIngress(namespace, ingressName)
	if err != nil {
		if errors.IsNotFound(err) {
			// Ingress 不存在，需创建
			// 同时创建路由规则
			pluginName := fmt.Sprintf("%s,%s,%s", model.KCPCredential, model.KCPBss, model.KCPCorsDefault)
			ingressNew := &networkingv1.Ingress{
				ObjectMeta: extentionsv1beta1.ObjectMeta{
					Name: ingressName,
					Annotations: map[string]string{
						"konghq.com/plugins":          pluginName,
						"konghq.com/preserve-host":    "false",
						"kubernetes.io/ingress.class": "kong",
					},
				},
				Spec: networkingv1.IngressSpec{
					Rules: []networkingv1.IngressRule{
						{
							Host: model.Base.HttpInvokeHost,
							IngressRuleValue: networkingv1.IngressRuleValue{
								HTTP: &networkingv1.HTTPIngressRuleValue{
									Paths: []networkingv1.HTTPIngressPath{
										// 路由规则
										ingressRoute,
									},
								},
							},
						},
					},
				},
			}
			_, err := fcClient.CreateIngress(namespace, ingressNew)
			return err
		} else {
			return err
		}
	}

	// Ingress 存在，添加路由规则
	if ingress.Spec.Rules[0].IngressRuleValue.HTTP == nil {
		ingress.Spec.Rules[0].IngressRuleValue.HTTP = &networkingv1.HTTPIngressRuleValue{
			Paths: []networkingv1.HTTPIngressPath{
				ingressRoute,
			},
		}
	} else {
		ingress.Spec.Rules[0].IngressRuleValue.HTTP.Paths = append(ingress.Spec.Rules[0].IngressRuleValue.HTTP.Paths, ingressRoute)
	}
	_, err = fcClient.UpdateIngress(namespace, ingress)
	return err
}

func CreateHttpTriggerService(fcClient *kubernetes.FcClient, triggerInfo *model.Trigger) error {
	namespace := model.Base.GatewayNamespace
	serviceName := getHttpTriggerServiceName(triggerInfo)
	externalName, _ := getHttpTriggerExternalName(triggerInfo)
	// HTTP 触发器配置
	triggerConfig, err := GetHttpTriggerConfig(triggerInfo)
	if err != nil {
		return err
	}
	pluginName := getHttpTriggerKongPluginName(triggerInfo)
	if triggerConfig.AuthType == model.HttpTriggerAuthTypeIAM {
		pluginName = fmt.Sprintf("%s,%s", pluginName, model.KCPIam)
	}
	service := &corev1.Service{
		ObjectMeta: extentionsv1beta1.ObjectMeta{
			Name: serviceName,
			Labels: map[string]string{
				"fc-trigger-name": triggerInfo.Name,
			},
			Annotations: map[string]string{
				"konghq.com/plugins":     pluginName,
				"konghq.com/host-header": externalName,
			},
		},
		Spec: corev1.ServiceSpec{
			Ports: []corev1.ServicePort{
				{
					Protocol:   corev1.ProtocolTCP,
					Port:       80,
					TargetPort: intstr.FromInt(80),
				},
			},
			Type:         corev1.ServiceTypeExternalName,
			ExternalName: externalName,
		},
	}
	_, err = fcClient.CreateService(namespace, service)
	if errors.IsAlreadyExists(err) {
		// 如果对应的资源已经存在，则删除后重建
		err = fcClient.DeleteService(namespace, serviceName)
		if err != nil {
			return err
		}
		_, err := fcClient.CreateService(namespace, service)
		return err
	}
	return err
}

func CreateHttpTriggerKongPlugin(fcClient *kubernetes.FcClient, triggerInfo *model.Trigger) error {
	namespace := model.Base.GatewayNamespace
	pluginName := getHttpTriggerKongPluginName(triggerInfo)
	configStr := `
{
  "add": {
    "headers": [
      "Ce-FcProxyMode:proxy",
      "Ce-FcAccountId:${fc-account-id}",
      "Ce-FcGroupName:${fc-group-name}",
      "Ce-FcFunctionName:${fc-function-name}",
      "Ce-FcFunctionQualifier:${fc-function-qualifier}"
    ]
  }
}`
	configStr = strings.Replace(configStr, "${fc-account-id}", triggerInfo.A5tId, 1)
	configStr = strings.Replace(configStr, "${fc-group-name}", triggerInfo.GroupName, 1)
	configStr = strings.Replace(configStr, "${fc-function-name}", triggerInfo.FuncName, 1)
	configStr = strings.Replace(configStr, "${fc-function-qualifier}", triggerInfo.TargetName, 1)
	kongPlugin := &kongConfigurationAPIV1.KongPlugin{
		ObjectMeta: extentionsv1beta1.ObjectMeta{
			Name: pluginName,
		},
		Config: apiextensionsv1.JSON{
			Raw: []byte(configStr),
		},
		PluginName: "request-transformer",
	}
	_, err := fcClient.CreateKongPlugin(namespace, kongPlugin)
	if errors.IsAlreadyExists(err) {
		// 如果对应的资源已经存在，则删除后重建
		err = fcClient.DeleteKongPlugin(namespace, pluginName)
		if err != nil {
			return err
		}
		_, err := fcClient.CreateKongPlugin(namespace, kongPlugin)
		return err
	}
	return err
}

func DeleteHttpTriggerResources(fcClient *kubernetes.FcClient, triggerInfo *model.Trigger) error {
	// 删除顺序，和创建顺序相反
	// 1. Ingress
	// 2. Service
	// 3. KongPlugin
	namespace := model.Base.GatewayNamespace
	ingressName := getHttpTriggerIngressName(triggerInfo)
	ingressPath := getHttpTriggerIngressPath(triggerInfo)
	serviceName := getHttpTriggerServiceName(triggerInfo)
	pluginName := getHttpTriggerKongPluginName(triggerInfo)
	// 查询 Ingress 资源
	ingress, err := fcClient.GetIngress(namespace, ingressName)
	if err != nil {
		return err
	}
	// 删除 Ingress 中 path 匹配的路由规则
	var newIngressPaths []networkingv1.HTTPIngressPath
	if ingress.Spec.Rules[0].HTTP != nil {
		for _, path := range ingress.Spec.Rules[0].HTTP.Paths {
			if path.Path != ingressPath {
				newIngressPaths = append(newIngressPaths, path)
			}
		}
	}
	if newIngressPaths == nil {
		ingress.Spec.Rules[0].HTTP = nil
	} else {
		ingress.Spec.Rules[0].HTTP.Paths = newIngressPaths
	}
	_, err = fcClient.UpdateIngress(namespace, ingress)
	if err != nil {
		return err
	}
	// 删除 Service
	err = fcClient.DeleteService(namespace, serviceName)
	if err != nil {
		return err
	}
	// 删除 KongPlugin
	err = fcClient.DeleteKongPlugin(namespace, pluginName)
	if err != nil {
		return err
	}

	return nil
}

func GetHttpTriggerConfig(triggerInfo *model.Trigger) (*model.HttpTriggerConfig, error) {
	triggerConfig := &model.HttpTriggerConfig{}
	err := json.Unmarshal([]byte(triggerInfo.Config), triggerConfig)
	if err != nil {
		return nil, err
	}
	return triggerConfig, nil
}

func getHttpTriggerIngressName(triggerInfo *model.Trigger) string {
	return fmt.Sprintf("default-%s", triggerInfo.A5tId)
}

func getHttpTriggerIngressPath(triggerInfo *model.Trigger) string {
	return fmt.Sprintf("/proxy/%s/%s/%s.%s",
		triggerInfo.A5tId, triggerInfo.GroupName, triggerInfo.FuncName, triggerInfo.TargetName)
}

func getHttpTriggerServiceName(triggerInfo *model.Trigger) string {
	_, externalNameMd5 := getHttpTriggerExternalName(triggerInfo)
	return fmt.Sprintf("http-%s", externalNameMd5)
}

func getHttpTriggerKongPluginName(triggerInfo *model.Trigger) string {
	_, externalNameMd5 := getHttpTriggerExternalName(triggerInfo)
	return fmt.Sprintf("plugin-%s", externalNameMd5)
}

// 获取 externalName 和对应的 Md5 值
func getHttpTriggerExternalName(triggerInfo *model.Trigger) (string, string) {
	// Service 命名规则
	// LATEST 版本：<functionName>--latest
	// 已发布版本：<functionName>--v<version>
	// 别名：<functionName>-<alias>
	externalName := ""
	if triggerInfo.TargetType == model.TargetTypeVersion {
		if triggerInfo.TargetName == model.FunctionLatestVersion {
			externalName = fmt.Sprintf("%s--latest.fc-%s-%s.svc.cluster.local",
				triggerInfo.FuncName, triggerInfo.A5tId, triggerInfo.GroupName)
		} else {
			externalName = fmt.Sprintf("%s--v%s.fc-%s-%s.svc.cluster.local",
				triggerInfo.FuncName, triggerInfo.TargetName, triggerInfo.A5tId, triggerInfo.GroupName)
		}
	} else if triggerInfo.TargetType == model.TargetTypeAlias {
		externalName = fmt.Sprintf("%s-%s.fc-%s-%s.svc.cluster.local",
			triggerInfo.FuncName, triggerInfo.TargetName, triggerInfo.A5tId, triggerInfo.GroupName)
	}
	// 获取 externalName 的哈希值做为资源名称的一部分
	w := sha256.New()
	_, _ = io.WriteString(w, externalName)
	externalNameMd5 := fmt.Sprintf("%x", w.Sum(nil))

	return externalName, externalNameMd5
}
